using System;
using System.Collections;
using Sedna.API.Enums;
using Sedna.Core.Persistence;
using Sedna.Domain;
using Iesi.Collections;
using NHibernate;
using NHibernate.Expression;
using Nullables;

namespace Sedna.API.Filter
{
    /// <summary>
    /// AccountEntry Filter. Specifies AccountEntry search criteria.
    /// </summary>
    [Serializable]
    public class AccountEntryFilter : AbstractFilterState, IHqlQueryFilterState
    {
        private Account account;
        private NullableDateTime bookDateFrom;
        private NullableDateTime bookDateTo;
        private NullableDateTime valueDateFrom;
        private NullableDateTime valueDateTo;
        private MonetaryAmount monetaryAmount;
        private Condition amountCondition;
        private ISet txTypes = new HashedSet();
        private AccountEntryType accountEntryType;

        #region Properties

        /// <summary>
        /// Target Account
        /// </summary>
        public Account Account
        {
            get { return account; }
            set { account = value; }
        }

        /// <summary>
        /// Min AccountEntry BookDate. If null, BookDate is not bounded below. 
        /// </summary>
        public NullableDateTime BookDateFrom
        {
            get { return bookDateFrom; }
            set { bookDateFrom = value; }
        }

        /// <summary>
        /// Max AccountEntry BookDate. If null, BookDate is not bounded above.
        /// </summary>
        public NullableDateTime BookDateTo
        {
            get { return bookDateTo; }
            set { bookDateTo = value; }
        }

        /// <summary>
        /// Min AccountEntry ValueDate. If null, ValueDate is not bounded below. 
        /// </summary>       
        public NullableDateTime ValueDateFrom
        {
            get { return valueDateFrom; }
            set { valueDateFrom = value; }
        }

        /// <summary>
        /// Max AccountEntry ValueDate. If null, ValueDate is not bounded above. 
        /// </summary>        
        public NullableDateTime ValueDateTo
        {
            get { return valueDateTo; }
            set { valueDateTo = value; }
        }

        /// <summary>
        /// Specifies AccountEntry Amount boundaries accourding to <see cref="AmountCondition"/>
        /// </summary>
        public MonetaryAmount MonetaryAmount
        {
            get { return monetaryAmount; }
            set { monetaryAmount = value; }
        }
        
        /// <summary>
        /// Condition used together with <see cref="MonetaryAmount"/> to specify AccountEntry Amount boundaries
        /// </summary>
        public Condition AmountCondition
        {
            get { return amountCondition; }
            set { amountCondition = value; }
        }

        /// <summary>
        /// Type of Transaction, that caused AccountEntry generation.
        /// </summary>
        public ISet TxTypes
        {
            get { return txTypes; }
        }
        
        
        /// <summary>
        /// AccountEntry type, i.e Debit or Credit or Any
        /// </summary>
        public AccountEntryType AccountEntryType
        {
            get { return accountEntryType; }
            set { accountEntryType = value; }
        }

        #endregion

        public IQuery ToHqlQuery(ISession session)
        {
            HqlQueryBuilder queryBuilder = new HqlQueryBuilder();
            queryBuilder.From.Add("AccountEntry as ae");
            queryBuilder.Eq("ae.Account", Account)
                .Between("ae.BookDate", BookDateFrom, BookDateTo)
                .Between("ae.ValueDate", ValueDateFrom, ValueDateTo)
                .Raw(AmountClause, MonetaryAmount!=null?(object)MonetaryAmount.Amount:null);
            if (AccountEntryType!=AccountEntryType.Any)
            {
                queryBuilder.Raw(AccountEntryTypeClause);
            }
            if (TxTypes.Count>0)
            {
                queryBuilder.Select.Add("ae");
                queryBuilder.From.Add("BusinessTxReason as btr");
                queryBuilder.In("btr.Transaction.Type", TxTypes);
                queryBuilder.Raw("ae.AccountingTx.AccountingTxReason = btr");                
            }
            return queryBuilder.ToQuery(session);
        }

        private string AccountEntryTypeClause
        {
            get
            {
                if (AccountEntryType==AccountEntryType.Credit)
                {
                    return "ae.Amount<0";
                }
                if (AccountEntryType==AccountEntryType.Debit)
                {
                    return "ae.Amount>0";
                }
                return null;
            }    
        }
        
        private string AmountClause
        {
            get
            {
                if (AmountCondition == Condition.Any)
                {
                    return null;
                }
                string sign = null;
                switch (AmountCondition)
                {
                    case Condition.Equal:
                        sign = "=";
                        break;
                    case Condition.GreaterOrEqual:
                        sign = ">=";
                        break;
                    case Condition.Greater:
                        sign = ">";
                        break;
                    case Condition.Less:
                        sign = "<";
                        break;
                    case Condition.LessOrEqual:
                        sign = "<=";
                        break;
                    default:
                        sign = "<>";
                        break;
                }

                return "ae.Amount" + sign + "{0}";
            }
        }
        
        private ICriterion AmountCriterion
        {
            get
            {
                if (AmountCondition==Condition.Any)
                {
                    return null;
                }
                
                switch (AmountCondition)
                {
                    case Condition.Equal:
                        return Expression.Eq("Amount", Amount);
                    case Condition.GreaterOrEqual:
                        return Expression.Ge("Amount", Amount);
                    case Condition.Greater:
                        return Expression.Gt("Amount", Amount);
                    case Condition.Less:
                        return Expression.Lt("Amount", Amount);
                    case Condition.LessOrEqual:
                        return Expression.Le("Amount", Amount);
                    default:
                        return Expression.Not(Expression.Eq("Amount", Amount));
                     
                }
            }
        }

    }
}
